/*
 *  Filename: rtc.c
 *  Function: RTC program for STM32F103
 *  Created: 2017-06-29
 *  Version: 1.0
 *  Author: Bin
 */

/*-------- Includes --------*/
#include "stm32f10x.h"

#include "rtc.h"

/*-------- Define --------*/
#define is_leap_year(year) ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)

/*-------- Global variables --------*/
uint8_t mon_table[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
char week_str[7][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
tm current_tm = {2017, 6, 29, 20, 47, 0};

/*-------- Private function prototypes --------*/
static void NVIC_Configure(void);
static void RTC_Configure(void);    // include RCC_Configuration

/*-------- Exported functions --------*/
void RTC_Init(void)
{
    // NVIC configuration
    NVIC_Configure();

    // if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) {
    if (1) {
        /* Backup data register value is not correct or not yet programmed (when
           the first time the program is executed) */

        /* RTC Configuration */
        RTC_Configure();

        /* Adjust time by values entered by the user on the hyperterminal */
        RTC_Set(&current_tm);

        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
    } else {
        /* No need to configure RTC */
        /* Wait for RTC registers synchronization */
        RTC_WaitForSynchro();

        /* Enable the RTC Second */
        RTC_ITConfig(RTC_IT_SEC, ENABLE);
        /* Wait until last write operation on RTC registers has finished */
        RTC_WaitForLastTask();
    }

    /* Clear reset flags */
    RCC_ClearFlag();
}

void RTC_Set(tm *p)
{
    uint32_t i, counter = 0;

    // count years
    counter += (p->year - 1970) * 365;
    counter += (p->year - 1969) / 4;  // leap years

    // count months
    for (i = 1; i < p->mon; i++)
        counter += (uint32_t)mon_table[i-1];

    if (is_leap_year(p->year) && p->mon > 2)
        counter += 1;

    // count days
    counter += p->day - 1;
    counter *= 86400;

    // count hours, minutes, seconds
    counter += p->hour * 3600;
    counter += p->min * 60;
    counter += p->sec;

    /* Wait until last write operation on RTC registers has finished */
    RTC_WaitForLastTask();
    /* Change the current time */
    RTC_SetCounter(counter);
    /* Wait until last write operation on RTC registers has finished */
    RTC_WaitForLastTask();
}

void RTC_Get(tm *p)
{
    uint32_t i, counter;
    uint32_t days;
    uint32_t day_sec;

    counter = RTC_GetCounter();
    days = counter / 86400;
    day_sec = counter % 86400;

    // get weekday
    p->wday = (days + 4) % 7;

    // get year
    p->year = 1970 + days / 365;
    days %= 365;
    days -= (p->year - 1969) / 4; // special case: leap year

    // get month
    days++; // date start from January 1st
    if (is_leap_year(p->year))
        mon_table[1] = 29;
    else
        mon_table[1] = 28;
    for (i = 0; days >= mon_table[i]; i++)
        days -= mon_table[i];
    p->mon = i + 1;

    // get day, hour, minute, second
    p->day = days;
    p->hour = day_sec / 3600; day_sec %= 3600;
    p->min = day_sec / 60; day_sec %= 60;
    p->sec = day_sec;
}

/*-------- RTC interrupt service routine --------*/
void RTC_IRQHandler(void)
{
  if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
  {
    /* Clear the RTC Second interrupt */
    RTC_ClearITPendingBit(RTC_IT_SEC);

    // update current time structure
    RTC_Get(&current_tm);

    /* Wait until last write operation on RTC registers has finished */
    RTC_WaitForLastTask();

  }
}

/*-------- Private functions --------*/
void NVIC_Configure(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;

    /* Configure one bit for preemption priority */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

    /* Enable the RTC Interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

void RTC_Configure(void)
{
    /* Enable PWR and BKP clocks */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

    /* Allow access to BKP Domain */
    PWR_BackupAccessCmd(ENABLE);

    /* Reset Backup Domain */
    BKP_DeInit();

    /* Enable LSE */
    // RCC_LSEConfig(RCC_LSE_ON);
    /* Wait till LSE is ready */
    // while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
    while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);

    /* Select LSE as RTC Clock Source */
    // RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    RCC_RTCCLKConfig(RCC_RTCCLKSource_HSE_Div128);

    /* Enable RTC Clock */
    RCC_RTCCLKCmd(ENABLE);

    /* Wait for RTC registers synchronization */
    RTC_WaitForSynchro();

    /* Wait until last write operation on RTC registers has finished */
    RTC_WaitForLastTask();

    /* Enable the RTC Second */
    RTC_ITConfig(RTC_IT_SEC, ENABLE);

    /* Wait until last write operation on RTC registers has finished */
    RTC_WaitForLastTask();

    /* Set RTC prescaler: set RTC period to 1sec */
    // RTC_SetPrescaler(32767); /* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) */
    RTC_SetPrescaler(62500 - 1); /* RTC frequency = 8MHz/128 = 62.5KHz */

    /* Wait until last write operation on RTC registers has finished */
    RTC_WaitForLastTask();
}
